#include "common/pch.h"

#include <cftc/reflect/class.h>
#include <cftc/reflect/field.h>

#include "object/global_transport.h"
#include "dbc/structure.h"

namespace battle::object
{

    GlobalTransport::GlobalTransport() : Transport()
    {
    }

    GlobalTransport::~GlobalTransport()
    {
    }

    bool GlobalTransport::Initialize(unsigned int entry, unsigned int period, std::string const& name)
    {
#if 0
        const GameObjectInfo* goinfo = ObjectMgr::GetGameObjectInfo(entry);
        if (!goinfo)
        {
            sLog.outErrorDb("Transport ID:%u, Name: %s, will not be loaded, gameobject_template missing", entry, name.c_str());
            return false;
        }

        if (goinfo->type != GAMEOBJECT_TYPE_MO_TRANSPORT)
        {
            sLog.outErrorDb("Transport ID:%u, Name: %s, will not be loaded, gameobject_template type wrong", entry, name.c_str());
            return false;
        }

        m_period = period;
        SetGOInfo(goinfo); //order is important. GenerateWaypoints needs m_goInfo access

        if (!GenerateWaypoints())
        {
            sLog.outErrorDb("Transport (path id %u) path size = 0. Transport ignored, check DBC files or transport GO data0 field.", goinfo->moTransport.taxiPathId);
            return false;
        }

        unsigned int mapid = m_WayPoints[0].mapid;

        // no global transports in dungeons
        const MapEntry* pMapInfo = sMapStore.LookupEntry(mapid);
        if (!pMapInfo || pMapInfo->Instanceable())
        {
            return false;
        }

        Map* m = sMapMgr.CreateMap(mapid, this);
        if (m == NULL)
        {
            return false;
        }

        float x = m_WayPoints[0].x;
        float y = m_WayPoints[0].y;
        float z = m_WayPoints[0].z;
        float o = 0.0f;

        SetMap(m);
        Relocate(x, y, z, o);

        if (!IsPositionValid())
        {
            sLog.outError("Transport ID:%u not created. Suggested coordinates are not valid: (X, Y, Z)=(%f, %f, %f)", goinfo->id, x, y, z);
            return false;
        }

        Object::_Create(goinfo->id, 0, HIGHGUID_MO_TRANSPORT);

        SetObjectScale(goinfo->size);
        SetGoType(GAMEOBJECT_TYPE_MO_TRANSPORT);
        Setunsigned intValue(GAMEOBJECT_FACTION, goinfo->faction);
        Setunsigned intValue(GAMEOBJECT_FLAGS, goinfo->flags);
        SetEntry(goinfo->id);
        Setunsigned intValue(GAMEOBJECT_DISPLAYID, goinfo->displayId);
        SetGoState(GO_STATE_READY);
        SetGoAnimProgress(GO_ANIMPROGRESS_DEFAULT);
        SetName(goinfo->name);
#endif
        return true;
    }

    struct keyFrame
    {
        explicit keyFrame(dbc::TaxiPathNodeEntry const& _node) : node(&_node),
            distSinceStop(-1.0f), distUntilStop(-1.0f), distFromPrev(-1.0f), tFrom(0.0f), tTo(0.0f)
        {
        }

        dbc::TaxiPathNodeEntry const* node;

        float distSinceStop;
        float distUntilStop;
        float distFromPrev;
        float tFrom, tTo;
    };

    bool GlobalTransport::GenerateWaypoints()
    {
#if 0
        unsigned int pathid = GetGOInfo()->moTransport.taxiPathId;

        if (pathid >= sTaxiPathNodesByPath.size())
        {
            return false;
        }

        TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathid];

        std::vector<keyFrame> keyFrames;
        int mapChange = 0;

        m_mapsUsed.clear();
        for (size_t i = 1; i < path.size() - 1; ++i)
        {
            if (mapChange == 0)
            {
                TaxiPathNodeEntry const& node_i = path[i];
                if (node_i.mapid == path[i + 1].mapid)
                {
                    keyFrame k(node_i);
                    keyFrames.push_back(k);
                    m_mapsUsed.insert(k.node->mapid);
                }
                else
                {
                    mapChange = 1;
                }
            }
            else
            {
                --mapChange;
            }
        }

        int lastStop = -1;
        int firstStop = -1;

        // first cell is arrived at by teleportation :S
        keyFrames[0].distFromPrev = 0;
        if (keyFrames[0].node->actionFlag == 2)
        {
            lastStop = 0;
        }

        // find the rest of the distances between key points
        for (size_t i = 1; i < keyFrames.size(); ++i)
        {
            if ((keyFrames[i].node->actionFlag == 1) || (keyFrames[i].node->mapid != keyFrames[i - 1].node->mapid))
            {
                keyFrames[i].distFromPrev = 0;
            }
            else
            {
                keyFrames[i].distFromPrev =
                    sqrt(pow(keyFrames[i].node->x - keyFrames[i - 1].node->x, 2) +
                        pow(keyFrames[i].node->y - keyFrames[i - 1].node->y, 2) +
                        pow(keyFrames[i].node->z - keyFrames[i - 1].node->z, 2));
            }
            if (keyFrames[i].node->actionFlag == 2)
            {
                // remember first stop frame
                if (firstStop == -1)
                {
                    firstStop = i;
                }
                lastStop = i;
            }
        }

        float tmpDist = 0;
        for (size_t i = 0; i < keyFrames.size(); ++i)
        {
            int j = (i + lastStop) % keyFrames.size();
            if (keyFrames[j].node->actionFlag == 2)
            {
                tmpDist = 0;
            }
            else
            {
                tmpDist += keyFrames[j].distFromPrev;
            }
            keyFrames[j].distSinceStop = tmpDist;
        }

        for (int i = int(keyFrames.size()) - 1; i >= 0; --i)
        {
            int j = (i + (firstStop + 1)) % keyFrames.size();
            tmpDist += keyFrames[(j + 1) % keyFrames.size()].distFromPrev;
            keyFrames[j].distUntilStop = tmpDist;
            if (keyFrames[j].node->actionFlag == 2)
            {
                tmpDist = 0;
            }
        }

        for (size_t i = 0; i < keyFrames.size(); ++i)
        {
            if (keyFrames[i].distSinceStop < (30 * 30 * 0.5f))
            {
                keyFrames[i].tFrom = sqrt(2 * keyFrames[i].distSinceStop);
            }
            else
            {
                keyFrames[i].tFrom = ((keyFrames[i].distSinceStop - (30 * 30 * 0.5f)) / 30) + 30;
            }

            if (keyFrames[i].distUntilStop < (30 * 30 * 0.5f))
            {
                keyFrames[i].tTo = sqrt(2 * keyFrames[i].distUntilStop);
            }
            else
            {
                keyFrames[i].tTo = ((keyFrames[i].distUntilStop - (30 * 30 * 0.5f)) / 30) + 30;
            }

            keyFrames[i].tFrom *= 1000;
            keyFrames[i].tTo *= 1000;
        }

        //    for (int i = 0; i < keyFrames.size(); ++i) {
        //        sLog.outString("%f, %f, %f, %f, %f, %f, %f", keyFrames[i].x, keyFrames[i].y, keyFrames[i].distUntilStop, keyFrames[i].distSinceStop, keyFrames[i].distFromPrev, keyFrames[i].tFrom, keyFrames[i].tTo);
        //    }

        // Now we're completely set up; we can move along the length of each waypoint at 100 ms intervals
        // speed = max(30, t) (remember x = 0.5s^2, and when accelerating, a = 1 unit/s^2
        int t = 0;
        bool teleport = false;
        if (keyFrames[keyFrames.size() - 1].node->mapid != keyFrames[0].node->mapid)
        {
            teleport = true;
        }

        WayPoint pos(keyFrames[0].node->mapid, keyFrames[0].node->x, keyFrames[0].node->y, keyFrames[0].node->z, teleport);
        m_WayPoints[0] = pos;
        t += keyFrames[0].node->delay * 1000;

        unsigned int cM = keyFrames[0].node->mapid;
        for (size_t i = 0; i < keyFrames.size() - 1; ++i)
        {
            float d = 0;
            float tFrom = keyFrames[i].tFrom;
            float tTo = keyFrames[i].tTo;

            // keep the generation of all these points; we use only a few now, but may need the others later
            if (((d < keyFrames[i + 1].distFromPrev) && (tTo > 0)))
            {
                while ((d < keyFrames[i + 1].distFromPrev) && (tTo > 0))
                {
                    tFrom += 100;
                    tTo -= 100;

                    if (d > 0)
                    {
                        float newX, newY, newZ;
                        newX = keyFrames[i].node->x + (keyFrames[i + 1].node->x - keyFrames[i].node->x) * d / keyFrames[i + 1].distFromPrev;
                        newY = keyFrames[i].node->y + (keyFrames[i + 1].node->y - keyFrames[i].node->y) * d / keyFrames[i + 1].distFromPrev;
                        newZ = keyFrames[i].node->z + (keyFrames[i + 1].node->z - keyFrames[i].node->z) * d / keyFrames[i + 1].distFromPrev;

                        teleport = false;
                        if (keyFrames[i].node->mapid != cM)
                        {
                            teleport = true;
                            cM = keyFrames[i].node->mapid;
                        }

                        //                    sLog.outString("T: %d, D: %f, x: %f, y: %f, z: %f", t, d, newX, newY, newZ);
                        pos = WayPoint(keyFrames[i].node->mapid, newX, newY, newZ, teleport);
                        if (teleport)
                        {
                            m_WayPoints[t] = pos;
                        }
                    }

                    if (tFrom < tTo)                            // caught in tFrom dock's "gravitational pull"
                    {
                        if (tFrom <= 30000)
                        {
                            d = 0.5f * (tFrom / 1000) * (tFrom / 1000);
                        }
                        else
                        {
                            d = 0.5f * 30 * 30 + 30 * ((tFrom - 30000) / 1000);
                        }
                        d = d - keyFrames[i].distSinceStop;
                    }
                    else
                    {
                        if (tTo <= 30000)
                        {
                            d = 0.5f * (tTo / 1000) * (tTo / 1000);
                        }
                        else
                        {
                            d = 0.5f * 30 * 30 + 30 * ((tTo - 30000) / 1000);
                        }
                        d = keyFrames[i].distUntilStop - d;
                    }
                    t += 100;
                }
                t -= 100;
            }

            if (keyFrames[i + 1].tFrom > keyFrames[i + 1].tTo)
            {
                t += 100 - ((long)keyFrames[i + 1].tTo % 100);
            }
            else
            {
                t += (long)keyFrames[i + 1].tTo % 100;
            }

            teleport = false;
            if ((keyFrames[i + 1].node->actionFlag == 1) || (keyFrames[i + 1].node->mapid != keyFrames[i].node->mapid))
            {
                teleport = true;
                cM = keyFrames[i + 1].node->mapid;
            }

            pos = WayPoint(keyFrames[i + 1].node->mapid, keyFrames[i + 1].node->x, keyFrames[i + 1].node->y, keyFrames[i + 1].node->z, teleport);

            //        sLog.outString("T: %d, x: %f, y: %f, z: %f, t:%d", t, pos.x, pos.y, pos.z, teleport);

            // if (teleport)
            m_WayPoints[t] = pos;

            t += keyFrames[i + 1].node->delay * 1000;
            //        sLog.outString("------");
        }

        unsigned int timer = t;

        //    sLog.outDetail("    Generated %lu waypoints, total time %u.", (unsigned long)m_WayPoints.size(), timer);

        m_next = m_WayPoints.begin();                           // will used in MoveToNextWayPoint for init m_curr
        MoveToNextWayPoint();                                   // m_curr -> first point
        MoveToNextWayPoint();                                   // skip first point

        m_pathTime = timer;

        m_nextNodeTime = m_curr->first;
#endif
        return true;
    }

    void GlobalTransport::MoveToNextWayPoint()
    {
        m_curr = m_next;

        ++m_next;
        if (m_next == m_WayPoints.end())
        {
            m_next = m_WayPoints.begin();
        }
    }

    void GlobalTransport::TeleportTransport(unsigned int newMapid, float x, float y, float z)
    {
#if 0
        Map const* oldMap = GetMap();
        Map* newMap = sMapMgr.CreateMap(newMapid, this);
        SetMap(newMap);
        Relocate(x, y, z);

        for (UnitSet::iterator itr = m_passengers.begin(); itr != m_passengers.end();)
        {
            UnitSet::iterator it2 = itr;
            ++itr;

            Unit* unit = *it2;
            if (!unit)
            {
                m_passengers.erase(it2);
                continue;
            }

            if (Player* plr = unit->ToPlayer())
            {
                if (plr->IsDead() && !plr->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
                {
                    plr->ResurrectPlayer(1.0);
                }
                plr->TeleportTo(newMapid, x, y, z, GetOrientation(), TELE_TO_NOT_LEAVE_TRANSPORT);
            }
        }

        if (oldMap != newMap)
        {
            UpdateForMap(oldMap);
            UpdateForMap(newMap);
        }
#endif
    }

    void GlobalTransport::Update(unsigned int /*update_diff*/, unsigned int /*p_time*/)
    {
        if (m_WayPoints.size() <= 1)
        {
            return;
        }
#if 0
        m_timer = GameTime::GetGameTimeMS() % m_period;
        while (((m_timer - m_curr->first) % m_pathTime) > ((m_next->first - m_curr->first) % m_pathTime))
        {
            MoveToNextWayPoint();

            // first check help in case client-server transport coordinates de-synchronization
            if (m_curr->second.mapid != GetMapId() || m_curr->second.teleport)
            {
                TeleportTransport(m_curr->second.mapid, m_curr->second.x, m_curr->second.y, m_curr->second.z);
            }
            else
            {
                Relocate(m_curr->second.x, m_curr->second.y, m_curr->second.z);
            }

            m_nextNodeTime = m_curr->first;

            if (m_curr == m_WayPoints.begin())
            {
                DETAIL_FILTER_LOG(LOG_FILTER_TRANSPORT_MOVES, " ************ BEGIN ************** %s", GetName());
            }

            DETAIL_FILTER_LOG(LOG_FILTER_TRANSPORT_MOVES, "%s moved to %f %f %f %d", GetName(), m_curr->second.x, m_curr->second.y, m_curr->second.z, m_curr->second.mapid);
        }
#endif
    }

    void GlobalTransport::UpdateForMap(Map const* targetMap)
    {
#if 0
        Map::PlayerList const& pl = targetMap->GetPlayers();
        if (pl.isEmpty())
        {
            return;
        }

        if (GetMapId() == targetMap->GetId())
        {
            for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
            {
                if (this != itr->getSource()->GetTransport())
                {
                    UpdateData transData;
                    BuildCreateUpdateBlockForPlayer(&transData, itr->getSource());
                    WorldPacket packet;
                    transData.BuildPacket(&packet, true);
                    itr->getSource()->SendDirectMessage(&packet);
                }
            }
        }
        else
        {
            UpdateData transData;
            BuildOutOfRangeUpdateBlock(&transData);
            WorldPacket out_packet;
            transData.BuildPacket(&out_packet, true);

            for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
                if (this != itr->getSource()->GetTransport())
                {
                    itr->getSource()->SendDirectMessage(&out_packet);
                }
        }
#endif
    }


}